﻿using System;
using System.Runtime.InteropServices;


public static class OpencoreCodec
{
    const int SAMPLE_FRAME_RATE = 8000;
    //enum Mode
    //{
    //    MR475 = 0,/* 4.75 kbps */
    //    MR515,    /* 5.15 kbps */
    //    MR59,     /* 5.90 kbps */
    //    MR67,     /* 6.70 kbps */
    //    MR74,     /* 7.40 kbps */
    //    MR795,    /* 7.95 kbps */
    //    MR102,    /* 10.2 kbps */
    //    MR122,    /* 12.2 kbps */
    //    MRDTX,    /* DTX       */
    //    N_MODES   /* Not Used  */
    //};

#if UNITY_IOS && !UNITY_EDITOR
    const string amr = "__Internal";
#else
    const string amr = "amr-codec";
#endif
    [DllImport(amr, EntryPoint = "Decoder_Interface_init")]
    private static extern IntPtr Decoder_Interface_init();

    [DllImport(amr, EntryPoint = "Decoder_Interface_exit")]
    private static extern void Decoder_Interface_exit(IntPtr state);

    [DllImport(amr, EntryPoint = "Decoder_Interface_Decode")]
    private static extern int Decoder_Interface_Decode(IntPtr state,[In, MarshalAs(UnmanagedType.LPArray)] byte[] inputBuffer, [Out, MarshalAs(UnmanagedType.LPArray)] short[] outputBuffer);

    [DllImport(amr, EntryPoint = "Encoder_Interface_init")]
    private static extern IntPtr Encoder_Interface_init(int dtx);

    [DllImport(amr, EntryPoint = "Encoder_Interface_exit")]
    private static extern void Encoder_Interface_exit(IntPtr state);

    [DllImport(amr, EntryPoint = "Encoder_Interface_Encode")]
    private static extern int Encoder_Interface_Encode(IntPtr state, int mode, [In, MarshalAs(UnmanagedType.LPArray)] short[] inputBuffer, [Out, MarshalAs(UnmanagedType.LPArray)] byte[] outputBuffer);

    private static byte[] FileHeader;

    private static byte[] EncodeOutputBuffer;
    const int DEFAULT_ENCODE_OUTPUT_BUFFER_SIZE = 10 * 1024;
    private static short[] EncodeInputProcessBuffer;
    const int ENCODE_INPUT_PROCESS_BUFFER = 160;
    private static byte[] EncodeProcessBuffer;
    const int ENCODE_PROCESS_BUFFER_SIZE = 500;
    

    private static short[] DecodeOutputBuffer;
    const int DEFAULT_DECODE_OUTPUT_BUFFER_SIZE = 10 * 1024;
    private static int[] sizes = new int[]{ 12, 13, 15, 17, 19, 20, 26, 31, 5, 6, 5, 5, 0, 0, 0, 0 };
    private static byte[] DecodeInputProcessBuffer;
    const int DECODE_INPUT_PROCESS_BUFFER_SIZE = 32;
    private static short[] DecodeProcessBuffer;
    const int DECODE_PROCESS_BUFFER_SIZE = 160;

    static OpencoreCodec()
    {
        FileHeader = System.Text.Encoding.ASCII.GetBytes("#!AMR\n");

        EncodeOutputBuffer = new byte[DEFAULT_ENCODE_OUTPUT_BUFFER_SIZE];
        EncodeProcessBuffer = new byte[ENCODE_PROCESS_BUFFER_SIZE];
        EncodeInputProcessBuffer = new short[ENCODE_INPUT_PROCESS_BUFFER];

        DecodeOutputBuffer = new short[DEFAULT_DECODE_OUTPUT_BUFFER_SIZE];
        DecodeProcessBuffer = new short[DECODE_PROCESS_BUFFER_SIZE];
        DecodeInputProcessBuffer = new byte[DECODE_INPUT_PROCESS_BUFFER_SIZE];
    }

    public static bool encode(short[] input,int len, int mode, out byte[] output, out int size)
    {
        output = null;
        size = 0;
        try
        {
            Array.Copy(FileHeader, 0, EncodeOutputBuffer, 0, FileHeader.Length);
            IntPtr encoder = Encoder_Interface_init(0);

            int processPos = 0;
            int outputPos = FileHeader.Length;
            while (processPos < len)
            {
                if (input.Length - processPos < ENCODE_INPUT_PROCESS_BUFFER)
                    break;

                Array.Copy(input, processPos, EncodeInputProcessBuffer, 0, ENCODE_INPUT_PROCESS_BUFFER);
                processPos += ENCODE_INPUT_PROCESS_BUFFER;

                int n = Encoder_Interface_Encode(encoder, mode, EncodeInputProcessBuffer, EncodeProcessBuffer);

                if (EncodeOutputBuffer.Length - outputPos < n)
                {
                    byte[] tmp = new byte[EncodeOutputBuffer.Length * 2];
                    Array.Copy(EncodeOutputBuffer, 0, tmp, 0, EncodeOutputBuffer.Length);
                    EncodeOutputBuffer = tmp;
                }
                Array.Copy(EncodeProcessBuffer, 0, EncodeOutputBuffer, outputPos, n);
                outputPos += n;
            }

            output = EncodeOutputBuffer;
            size = outputPos;
            return true;
        }
        catch(Exception e)
        {
            return false;
        }
    }

    public static bool decode(byte[] input, int len, out short[] output, out int size)
    {
        output = null;
        size = 0;
        try
        {
            IntPtr decoder = Decoder_Interface_init();
            int decodePos = FileHeader.Length;
            int outputPos = 0;

            while (decodePos < len)
            {
                int blockSize = sizes[(input[decodePos] >> 3) & 0x0f];
                if (blockSize == 0)
                {
                    break;
                }
                DecodeInputProcessBuffer[0] = input[decodePos];
                if(decodePos + 1 >= input.Length)
                {
                    break;
                }
                Array.Copy(input, decodePos + 1, DecodeInputProcessBuffer, 1, blockSize);
                decodePos += (blockSize + 1);
                

                Decoder_Interface_Decode(decoder, DecodeInputProcessBuffer, DecodeProcessBuffer);
                if (DecodeOutputBuffer.Length - outputPos < DECODE_PROCESS_BUFFER_SIZE)
                {
                    short[] tmp = new short[DecodeOutputBuffer.Length * 2];
                    Array.Copy(DecodeOutputBuffer, 0, tmp, 0, DecodeOutputBuffer.Length);
                    DecodeOutputBuffer = tmp;
                }
                Array.Copy(DecodeProcessBuffer, 0, DecodeOutputBuffer, outputPos, DECODE_PROCESS_BUFFER_SIZE);
                outputPos += DECODE_PROCESS_BUFFER_SIZE;
            }

            output = DecodeOutputBuffer;
            size = outputPos;
            return true;
        }
        catch (Exception e)
        {
            return false;
        }
    }
}
